import { toNumber } from "ng-zorro-antd";

export class PcodeInterpreter {
  constructor(
    public instructions: string[],
    public printString: (str: string) => void,
    public nextInput: () => number
  ) {
    this.decorated_stack = this.get_decorated_stack();
  }
  public stack: (number | undefined)[] = [];
  public cursor = 0;
  public base = 0;
  public count = 0;
  public decorated_stack: {
    value: number | undefined;
    class: string[];
  }[] = [];
  public getLayerAddress(layer: number) {
    let result = this.base;
    for (let i = 0; i < layer; ++i) {
      result = this.stack[this.base];
    }
    return result;
  }
  nextInstruction(): [string, number, number] {
    if (
      !this.instructions ||
      this.cursor === undefined ||
      this.cursor > this.instructions.length ||
      this.instructions[this.cursor] === undefined
    ) {
      return undefined;
    }
    try {
      const [_, op, p1, p2] = this.instructions[this.cursor].match(
        /(\w{3})\s*(\d+)\s+(\d+)/
      );
      return [op.toLowerCase(), parseInt(p1), parseInt(p2)];
    } catch (e) {
      return undefined;
    }
  }
  next(): boolean {
    const instruction = this.nextInstruction();
    if (!instruction) {
      return false;
    }
    const [op, p1, p2] = instruction;
    this.count++;
    this.cursor++;
    switch (op) {
      case "int":
        switch (p1) {
          case 0:
            for (var i = 0; i < p2; ++i) {
              this.stack.push(undefined);
            }
            break;
          default:
            break;
        }
        break;
      case "opr":
        switch (p1) {
          case 0:
            switch (p2) {
              case 0:
                if (this.base + 2 >= this.stack.length) {
                  throw new Error(`所取的位置不在栈空间内`);
                }
                this.cursor = this.stack[this.base + 2];
                const temp = this.stack[this.base + 1];
                for (var i = this.stack.length - this.base; i > 0; i--) {
                  this.stack.pop();
                }
                this.base = temp;
                if (temp === undefined) {
                  this.base = 0;
                }
                break;
              case 1:
                if (!this.stack.length) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(-this.stack.pop());
                break;
              case 2:
                if (this.stack.length < 2) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(this.stack.pop() + this.stack.pop());
                break;
              case 3:
                if (this.stack.length < 2) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(-this.stack.pop() + this.stack.pop());
                break;
              case 4:
                if (this.stack.length < 2) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(this.stack.pop() * this.stack.pop());
                break;
              case 5:
                if (this.stack.length < 2) {
                  throw new Error(`空栈不能弹出`);
                }
                const [a, b] = [this.stack.pop(), this.stack.pop()];
                // 也可以写成~~(b/a)之类
                this.stack.push(Math.trunc(b / a));
                break;
              case 6:
                if (this.stack.length < 1) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(this.stack.pop() & 1);
                break;
              case 8:
                if (this.stack.length < 2) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(this.stack.pop() === this.stack.pop() ? 1 : 0);
                break;
              case 9:
                if (this.stack.length < 2) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(this.stack.pop() !== this.stack.pop() ? 1 : 0);
                break;
              case 10:
                if (this.stack.length < 2) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(this.stack.pop() > this.stack.pop() ? 1 : 0);
                break;
              case 11:
                if (this.stack.length < 2) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(this.stack.pop() <= this.stack.pop() ? 1 : 0);
                break;
              case 12:
                if (this.stack.length < 2) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(this.stack.pop() < this.stack.pop() ? 1 : 0);
                break;
              case 13:
                if (this.stack.length < 2) {
                  throw new Error(`空栈不能弹出`);
                }
                this.stack.push(this.stack.pop() >= this.stack.pop() ? 1 : 0);
                break;
              case 14:
                if (this.stack.length < 1) {
                  throw new Error(`空栈不能弹出`);
                }
                this.printString(this.stack.pop().toString());
                break;
              case 15:
                this.printString("⏎");
                break;
              case 16:
                this.stack.push(this.nextInput());
                break;
              default:
                break;
            }
            break;
          default:
            break;
        }
        break;
      case "cal":
        this.stack.push(this.getLayerAddress(p1));
        this.stack.push(this.base);
        this.stack.push(this.cursor);
        this.base = this.stack.length - 3;
        this.cursor = p2;
        break;
      case "lit":
        this.stack.push(p2);
        break;
      case "lod":
        if (this.getLayerAddress(p1) + p2 >= this.stack.length) {
          throw new Error(`所取的位置不在栈空间内`);
        }
        this.stack.push(this.stack[this.getLayerAddress(p1) + p2]);
        break;
      case "sto":
        if (this.getLayerAddress(p1) + p2 >= this.stack.length) {
          throw new Error(`所取的位置不在栈空间内`);
        }
        this.stack[this.getLayerAddress(p1) + p2] = this.stack.pop();
        break;
      case "jmp":
        this.cursor = p2;
        break;
      case "jpc":
        if (this.stack.length < 1) {
          throw new Error(`空栈不能弹出`);
        }
        if (this.stack.pop() === 0) {
          this.cursor = p2;
        }
        break;
      default:
        break;
    }
    this.decorated_stack = this.get_decorated_stack();
    return true;
  }
  get_decorated_stack() {
    // 修改位置
    var modified = undefined;
    // 新创建的值
    var created = [];
    // 移除数量
    var removed = 0;
    // 读取位置
    var fetched = undefined;
    const instruction = this.nextInstruction();
    if (!instruction) {
    } else {
      const [op, p1, p2] = instruction;
      switch (op.toLowerCase()) {
        case "int":
          switch (p1) {
            case 0:
              created = Array.from({ length: p2 }).map((x) => undefined);
              break;
            default:
              break;
          }
          break;
        case "opr":
          switch (p1) {
            case 0:
              switch (p2) {
                case 0:
                  removed = this.stack.length - this.base;
                  break;
                case 1:
                  modified = this.stack.length - 1;
                  break;
                case 2:
                case 3:
                case 4:
                case 5:
                  removed = 1;
                  modified = this.stack.length - 2;
                  break;
                case 6:
                  modified = this.stack.length - 1;
                  break;
                case 8:
                case 9:
                case 10:
                case 11:
                case 12:
                case 13:
                  removed = 1;
                  modified = this.stack.length - 2;
                  break;
                case 14:
                  removed = 1;
                  break;
                case 15:
                  break;
                case 16:
                  created = [undefined];
                  break;
                default:
                  break;
              }
              break;
            default:
              break;
          }
          break;
        case "cal":
          created = [this.getLayerAddress(p1), this.base, this.cursor];
          break;
        case "lit":
          created = [p2];
          break;
        case "lod":
          fetched = this.getLayerAddress(p1) + p2;
          created = [this.stack[fetched]];
          break;
        case "sto":
          modified = this.getLayerAddress(p1) + p2;
          removed = 1;
          break;
        case "jmp":
          break;
        case "jpc":
          removed = 1;
          break;
        default:
          break;
      }
    }
    var temp: {
      value: number | undefined;
      class: string[];
    }[] = this.stack.map((v, i) => ({
      value: v,
      class: [],
    }));
    if (modified !== undefined) {
      temp[modified]?.class?.push("tag-modified");
    }
    if (fetched !== undefined) {
      temp[fetched].class.push("tag-fetched");
    }
    if (temp.length > 0 && this.base !== undefined) {
      temp[this.base].class.push("tag-base");
    }
    // console.log(removed);
    for (var i = 0; i < removed; ++i) {
      temp[this.stack.length - 1 - i]?.class?.push("tag-removed");
    }
    temp = temp.concat([
      ...created.map((x) => ({
        value: x,
        class: ["tag-created"],
      })),
    ]);
    return temp;
  }
}
